import { ParseUrlOption } from './../common/http/rest-api';
import { businsessHttpServer } from './../common/http/http-server';
import { Subject, Observable } from 'rxjs';
import { parseUrl, httpResultCallback } from "../common/http/rest-api";

export interface Id{
   id:string 
}

export interface Action{
    type:string;
    payload:any;
}

//todo baseReducer
export type Reducer<T> = (state:Array<T>, action:Action)=>Array<T>


function lazyLoad<T>(observable: Observable<T>, loader: () => any): Observable<T> {
    let loaded = false;
    return Observable.defer(() => {
        if (!loaded) {
            Promise.resolve().then(loader).then(() => loaded = true);
        }
        return observable;
    });
}

export class Repositoty<T extends Id>{
    name:string;
    private initParseUrlOption:ParseUrlOption;
    private resetInit$ = new Subject();
    initErrorHandler : (error)=>any;
    constructor(name:string,initParseUrlOption:ParseUrlOption,accumulator:Reducer<T>){
        if(!initParseUrlOption) return;
        this.name = name;
        this.initParseUrlOption = initParseUrlOption;
        this.source$ = Observable.defer<T[]>(() => {
            return this.get(this.initParseUrlOption)
        }).catch((err)=>{
            console.error(err);
            this.initErrorHandler && this.initErrorHandler(err);
            return null;
        }).repeatWhen(()=>this.resetInit$).retryWhen(errorObs => errorObs.delay(3000)).publishReplay(1).refCount();;
        const result$ = this.source$.map(data=>{
            return {
                type:"seed",
                payload:data
            }
        }).merge(this.dispatch$).scan(accumulator,[]).publishReplay(1);
        this.result$ = result$.debounceTime(100);
        result$.connect();
    }

    protected get(parseUrlOption:ParseUrlOption){
        return httpResultCallback(businsessHttpServer(parseUrl(`/api/${this.name}`,parseUrlOption)).get())
    }

    protected post(parseUrlOption:ParseUrlOption,body){
        return httpResultCallback(businsessHttpServer(parseUrl(`/api/${this.name}`,parseUrlOption)).post(body))
    }

    protected put<T>(parseUrlOption:ParseUrlOption,body):Promise<T>{
        return httpResultCallback(businsessHttpServer(parseUrl(`/api/${this.name}`,parseUrlOption)).put(body))
    }

    setInitParseUrlOption(initParseUrlOption:ParseUrlOption){
        this.initParseUrlOption = initParseUrlOption;
    }

    getInitParseUrlOption(){
        return this.initParseUrlOption;
    }

    reset(){
        this.resetInit$.next();
    }

    getObservableByPredication(predication: (entity:T) => boolean) {
        return this.result$.map(entities => entities.filter(predication));
    }

    getFirstObservableByCondition(condition: (entity:T) => boolean = (entity)=>true){
        return this.getObservable().map(entities=>entities.filter(entitiy=>condition(entitiy))).first()
    }

    getObservable(){
        return this.result$;
    }

    changeEntity(entity:T){
        this.disPatch({
            type:"change",
            payload:entity
        });
    }

    changeEntities(entities:T[]){
        entities.forEach(entity=>{
            this.disPatch({
                type:"change",
                payload:entity
            });
        })
    }
    getEntityObservableById(id:string):Observable<T>{
        return this.result$.map(x=>x.find(entity=>entity.id == id)).distinctUntilChanged().debounceTime(50);
    }

    getEntitybleById(id:string):Promise<T>{
        return this.getEntityObservableById(id).toPromise();
    }

    disPatch(action:Action){
        this.dispatch$.next(action)
    }

    private dispatch$ = new Subject<Action>();
    private source$:Observable<Array<T>>
    private result$:Observable<Array<T>>
}


export class RepositotiesManager{
    repositoties = new Set<Repositoty<any>>();
    register(repositoty){
        this.repositoties.add(repositoty);
    }

    getRepositotyByName<T extends Id>(name:String):Repositoty<T>{
        return Array.from(this.repositoties).find(x=>x.name == name)
    }
}